<template>
  <el-card class="box-card">
    <template #header>
      <div class="card-header">
        <div class="left">
          <el-space wrap>
            <el-upload
              class="file-upload"
              ref="upload"
              action="http://localhost:8085/files/upload"
              :auto-upload="true"
              :before-upload="() => (buttonLoading.uploadFile = true)"
              :on-success="handleUploadSuccess"
              :show-file-list="false"
              multiple
            >
              <el-button type="primary" :icon="UploadFilled" :loading="buttonLoading.uploadFile"
                >上传文件</el-button
              >
            </el-upload>
            <!-- <el-button :icon="FolderOpened" :loading="buttonLoading.mkdir">创建目录</el-button> -->
            <el-button
              :icon="Download"
              :loading="buttonLoading.batchDownload"
              @click="handleBatchDownload"
              >批量下载</el-button
            >
            <el-button
              :icon="Link"
              :loading="buttonLoading.batchExportLink"
              @click="handleBatchExportLink"
              >导出外链</el-button
            >
            <el-button
              :icon="Delete"
              :loading="buttonLoading.batchDelete"
              @click="handleBatchDelete"
              >批量删除</el-button
            >
            <el-button :icon="Refresh" :loading="buttonLoading.refresh" @click="handleRefresh"
              >刷新</el-button
            >
          </el-space>
        </div>
        <div class="right">
          <el-input
            v-model="queryParams.keywords"
            placeholder="请输入文件名按回车搜索"
            clearable
            @keyup.enter="handleSearch"
            @clear="handleSearch"
          >
            <template #append>
              <el-button :icon="Search" :loading="buttonLoading.search" @click="handleSearch" />
            </template>
          </el-input>
        </div>
      </div>
    </template>
    <el-table
      ref="multipleTableRef"
      :data="fileList"
      style="width: 100%"
      height="620"
      v-loading="tableLoading"
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" />
      <el-table-column prop="filename" label="文件名" />
      <el-table-column prop="filepath" width="180" align="center">
        <template #header>
          缩略图（点击预览）
          <el-tooltip
            content="仅支持图片、视频、word、excel、ppt、pdf、markdown、txt 资源的预览"
            placement="top-start"
          >
            <el-icon color="#0781d7"><QuestionFilled /></el-icon>
          </el-tooltip>
        </template>
        <template #default="{ row }">
          <template v-if="row.mimetype.startsWith('image/')">
            <el-image
              class="thumb"
              :lazy="true"
              :src="row.filepath"
              :preview-src-list="previewFileList"
              :initial-index="previewFileList.findIndex((item) => item === row.filepath)"
              :hide-on-click-modal="true"
              fit="contain"
              :preview-teleported="true"
            />
          </template>
          <template v-else>
            <el-icon class="thumb" @click="handlePreview(row)">
              <SvgIcon iconClass="video" v-if="row.mimetype.startsWith('video/')" />
              <SvgIcon iconClass="word" v-else-if="row.mimetype.endsWith('.document')" />
              <SvgIcon iconClass="pdf" v-else-if="row.mimetype.endsWith('/pdf')" />
              <SvgIcon iconClass="txt" v-else-if="row.mimetype === 'text/plain'" />
              <SvgIcon iconClass="xlsx" v-else-if="row.mimetype.endsWith('.sheet')" />
              <SvgIcon
                iconClass="markdown"
                v-else-if="row.mimetype === 'application/octet-stream'"
              />
              <SvgIcon iconClass="unknownFile" v-else />
            </el-icon>
          </template>
        </template>
      </el-table-column>
      <el-table-column prop="size" label="文件大小" width="180" align="center" />
      <el-table-column prop="mimetype" label="文件类型" width="200" align="center">
        <template #default="{ row }">
          <el-tooltip effect="dark" :content="row.mimetype" placement="top-start">
            {{ row.mimetype.replace(/^(.{10}).*(.{8})$/g, '$1……$2') }}
          </el-tooltip>
        </template>
      </el-table-column>
      <el-table-column prop="updateTime" label="最近更新" width="180" align="center" />
      <el-table-column label="操作" width="300" align="center">
        <template #default="{ row }">
          <!-- <el-button link type="primary">详情</el-button> -->
          <el-button link type="primary" @click="handleDownload(row.filepath)">下载</el-button>
          <el-button link type="primary" @click="handleCopyLink(row.filepath)">复制外链</el-button>
          <el-button link type="danger" @click="handleDelete(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      v-model:currentPage="queryParams.pageNum"
      v-model:page-size="queryParams.pageSize"
      :total="total"
      layout="total, sizes, prev, pager, next, jumper"
      background
      class="mt20 flex-end"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
    <el-dialog
      width="80%"
      v-model="dialogVisible"
      :title="`正在预览文件： ${previewFileParams.filename}`"
      @close="handleCloseDialog"
    >
      <template v-if="previewFileParams.mimetype.startsWith('video/')">
        <video :src="previewFileParams.filepath" controls class="w85p" autoplay loop>
          <source :src="previewFileParams.filepath" type="video/ogg" />
          <source :src="previewFileParams.filepath" type="video/mp4" />
        </video>
      </template>
      <template v-else-if="previewFileParams.mimetype.endsWith('.document')">
        <div ref="previewContainer"></div>
      </template>
      <template v-else-if="['text/plain', 'application/pdf'].includes(previewFileParams.mimetype)">
        <iframe
          width="100%"
          height="99%"
          frameborder="0"
          :src="previewFileParams.filepath"
        ></iframe>
      </template>
      <template v-else-if="previewFileParams.mimetype.endsWith('.sheet')">
        <div class="file-pre">
          <el-tabs v-model="activeName" type="border-card">
            <el-tab-pane
              v-for="(item, index) in excelSheet"
              :key="index"
              :label="item.name"
              :name="item.name"
            >
              <div class="table" v-html="item.innerHTML"></div>
            </el-tab-pane>
          </el-tabs>
        </div>
      </template>
      <template v-else-if="previewFileParams.mimetype === 'application/octet-stream'">
        <div class="markdown-container">
          <div v-highlight v-html="markdownArticle"></div>
        </div>
      </template>
    </el-dialog>
  </el-card>
</template>

<script lang="ts" setup>
import { computed, getCurrentInstance, nextTick, reactive, ref } from 'vue'
import { renderAsync } from 'docx-preview'
import {
  Search,
  UploadFilled,
  FolderOpened,
  Download,
  Delete,
  Refresh,
  Link,
  QuestionFilled
} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox, ElTable } from 'element-plus'
import { downloadFile, getFileByUrl, exportCsv } from '@/utils'
import type { WorkBook, XLSX$Utils } from 'xlsx/types/index'
import { marked } from 'marked'

export type FileInfo = {
  id: number
  filename: string
  size: number | string
  mimetype: string
  updateTime: string
  filepath: string
}

const queryParams = reactive({
  keywords: '',
  pageNum: 1,
  pageSize: 10
})
const fileList = ref<FileInfo[]>([])
const total = ref(0)
// 表格加载状态
const tableLoading = ref(false)
// 按钮加载状态
const buttonLoading = reactive({
  uploadFile: false,
  mkdir: false,
  batchDownload: false,
  batchExportLink: false,
  batchDelete: false,
  refresh: false,
  search: false
})
const dialogVisible = ref(false)
const previewContainer = ref<HTMLElement>()

const previewFileList = computed(() => {
  const list = fileList.value.filter((item) => item.mimetype.startsWith('image/'))
  return list.map((item) => item.filepath)
})
// 获取文件列表
const getFileList = async () => {
  tableLoading.value = true
  const url = `http://localhost:8085/files/list?pageNum=${queryParams.pageNum}&pageSize=${
    queryParams.pageSize
  }&filename=${queryParams.keywords || ''}`
  try {
    const res = await fetch(url).then<{
      code: string
      total: number
      data: FileInfo[]
    }>((res) => res.json())
    if (res.code === 'ECONNREFUSED') return ElMessage.error('无法连接服务器')
    for (const item of res.data) {
      const size = item.size
      item.size = size >= 1024 ? ((item.size as number) / 1024).toFixed(2) + ' KB' : size + ' B'
    }
    fileList.value = res.data
    total.value = res.total
  } catch (err) {
    if ((err as Error).message === 'Failed to fetch') {
      ElMessage.error('无法连接文件服务器！')
    }
  } finally {
    tableLoading.value = false
  }
}
getFileList()

// 搜索文件
const handleSearch = async () => {
  buttonLoading.search = true
  queryParams.pageNum = 1
  await getFileList()
  buttonLoading.search = false
}

// 文件上传成功
const handleUploadSuccess = async () => {
  buttonLoading.uploadFile = false
  getFileList()
}

// 删除文件
const handleDelete = (ids: number | string, cb?: () => void) => {
  ElMessageBox.confirm('您确定要执行删除操作吗？', '删除', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      fetch(`http://localhost:8085/files/delete/${ids}`, {
        method: 'delete'
      })
        .then((res) => res.json())
        .then((res) => {
          cb && cb()
          ElMessage({
            message: res.message,
            type: 'success'
          })
          getFileList()
        })
    })
    .catch(() => {
      cb && cb()
    })
}

const multipleSelection = ref<FileInfo[]>([])

// 多选状态改变
const handleSelectionChange = (val: FileInfo[]) => {
  multipleSelection.value = val
}

// 批量删除文件
const handleBatchDelete = () => {
  if (multipleSelection.value.length == 0) return ElMessage.warning('请选择要删除的文件')
  buttonLoading.batchDelete = true
  const fileIds = multipleSelection.value.map((item) => item.id).toString()
  handleDelete(fileIds, () => (buttonLoading.batchDelete = false))
}

// 刷新列表
const handleRefresh = async () => {
  buttonLoading.refresh = true
  queryParams.keywords = ''
  queryParams.pageNum = 1
  await getFileList()
  buttonLoading.refresh = false
}

// 复制外链
const handleCopyLink = (filepath: string) => {
  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard.writeText(filepath).then(() => {
      ElMessage.success(`复制成功！`)
    })
  } else {
    // 非安全域使用 document.execCommand('copy')
    // 创建textArea
    const textArea = document.createElement('textarea')
    textArea.value = filepath
    document.body.appendChild(textArea)
    textArea.focus()
    textArea.select()
    document.execCommand('copy')
    textArea.remove()
    ElMessage.success(`复制成功！`)
  }
}

// 下载文件
const handleDownload = (filepath: string) => {
  downloadFile(filepath)
}

const multipleTableRef = ref<InstanceType<typeof ElTable>>()

// 批量下载文件
const handleBatchDownload = () => {
  if (multipleSelection.value.length == 0) return ElMessage.warning('请选择要下载的文件')
  buttonLoading.batchDownload = true
  const filepathList = multipleSelection.value.map((item) => item.filepath)
  downloadFile(
    filepathList.length === 1 ? filepathList[0] : JSON.parse(JSON.stringify(filepathList)),
    () => (buttonLoading.batchDownload = false)
  )
  multipleTableRef.value!.clearSelection()
}

// 导出外链
const handleBatchExportLink = () => {
  if (multipleSelection.value.length == 0) return ElMessage.warning('请选择要导出的文件')
  const obj = {
    titleList: ['文件名', '外链地址'],
    data: multipleSelection.value.map((item) => ({
      filename: item.filename,
      filepath: item.filepath
    }))
  }
  exportCsv(obj)
}

const handleSizeChange = (val: number) => {
  queryParams.pageSize = val
  queryParams.pageNum = 1
  getFileList()
}
const handleCurrentChange = (val: number) => {
  queryParams.pageNum = val
  getFileList()
}

// 预览文件参数
const previewFileParams = reactive({
  mimetype: '',
  filepath: '',
  filename: ''
})

const xlsx = getCurrentInstance()!.appContext.config.globalProperties.$xlsx
const markdownArticle = ref<string>('')
// 预览
const handlePreview = async (fileInfo: FileInfo) => {
  previewFileParams.mimetype = fileInfo.mimetype
  previewFileParams.filepath = fileInfo.filepath
  previewFileParams.filename = fileInfo.filename
  dialogVisible.value = true

  if (fileInfo.mimetype.startsWith('video/')) {
    previewFileParams.filepath = fileInfo.filepath
  } else if (previewFileParams.mimetype.endsWith('.document')) {
    const blob = await getFileByUrl(previewFileParams.filepath)
    nextTick(() => {
      renderAsync(blob, previewContainer.value!, undefined, {
        className: 'docx', // 默认和文档样式类的类名/前缀
        inWrapper: true, // 启用围绕文档内容渲染包装器
        ignoreWidth: false, // 禁止页面渲染宽度
        ignoreHeight: false, // 禁止页面渲染高度
        ignoreFonts: false, // 禁止字体渲染
        breakPages: true, // 在分页符上启用分页
        ignoreLastRenderedPageBreak: true, //禁用lastRenderedPageBreak元素的分页
        experimental: false, //启用实验性功能（制表符停止计算）
        trimXmlDeclaration: true, //如果为真，xml声明将在解析之前从xml文档中删除
        debug: false // 启用额外的日志记录
      }) // 渲染到页面
    })
  } else if (previewFileParams.mimetype.endsWith('.sheet')) {
    const blob = await getFileByUrl(previewFileParams.filepath)
    var arrayPromise = new Promise(function (resolve) {
      var reader = new FileReader()

      reader.onloadend = function () {
        resolve(reader.result)
      }

      reader.readAsArrayBuffer(blob)
    })

    arrayPromise.then(function (array) {
      // 预览 excel
      const workbook: WorkBook = xlsx.read(array, { type: 'array' })

      // 删除掉没有数据的sheet
      Object.values(workbook.Sheets).forEach((sheet, index) => {
        if (Object.keys(sheet).indexOf('!ref') === -1) {
          delete workbook.Sheets[workbook.SheetNames[index]]
        }
      })
      tableToHtml(workbook)
    })
  } else if (previewFileParams.mimetype === 'application/octet-stream') {
    // 展示 markdown
    const markdownContent = await fetch(previewFileParams.filepath).then((res) => res.text())
    // 调用marked()方法，将markdown转换成html
    markdownArticle.value = marked(markdownContent)
  } else if (['text/plain', 'application/pdf'].includes(previewFileParams.mimetype)) {
    // 什么都不用做
  } else {
    dialogVisible.value = false
    ElMessage.warning('该文件暂不支持预览')
  }
}

const excelSheet = ref<
  {
    name: string
    innerHTML: string
  }[]
>([])
const activeName = ref('')
const tableToHtml = (workbook: WorkBook) => {
  const sheetList = workbook.SheetNames.filter((v) => v.indexOf('数据源') === -1)
  activeName.value = sheetList[0]
  sheetList.forEach((sheet) => {
    const worksheet = workbook.Sheets[sheet]
    if (worksheet) {
      const innerHTML = (xlsx.utils as XLSX$Utils).sheet_to_html(worksheet)
      excelSheet.value.push({
        name: sheet,
        innerHTML: innerHTML
      })
    } else {
      excelSheet.value.push({
        name: sheet,
        innerHTML: '暂无数据'
      })
    }
  })
}

// 关闭预览
const handleCloseDialog = () => {
  previewFileParams.filepath = ''
  previewFileParams.mimetype = ''
  previewFileParams.filename = ''
  activeName.value = ''
  excelSheet.value = []
}
</script>

<style lang="scss" scoped>
.box-card {
  height: 100%;
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    .file-upload {
      display: inline-block;
    }
  }

  .thumb {
    width: 60px;
    height: 60px;
    font-size: 18px;
    cursor: pointer;
  }

  :deep(.el-table__cell .cell) {
    overflow: hidden;
    text-overflow: ellipsis;
    /*弹性伸缩盒子模型显示*/
    display: -webkit-box;
    /*限制在一个块元素显示的文本的行数*/
    -webkit-line-clamp: 2;
    /*设置或检索伸缩盒对象的子元素的排列方式*/
    -webkit-box-orient: vertical;
  }

  :deep(.el-dialog) {
    display: flex;
    flex-direction: column;
    margin: 40px auto !important;
    height: 90%;
    overflow: hidden;
    .el-dialog__body {
      flex: 1;
      padding: 0;
      margin: 10px;
      overflow: auto;
      text-align: center;
    }
  }

  :deep(.file-pre) {
    min-height: 100%;
    .table-html-wrap table {
      border-right: 1px solid #e8eaec;
      border-bottom: 1px solid #e8eaec;
      border-collapse: collapse;
      margin: auto;
    }

    .table-html-wrap table td {
      border-left: 1px solid #e8eaec;
      border-top: 1px solid #e8eaec;
      white-space: wrap;
      text-align: left;
      min-width: 100px;
      padding: 4px;
    }

    table {
      border-top: 1px solid #ebeef5;
      border-left: 1px solid #ebeef5;
      width: 100%;
      overflow: auto;

      tr {
        height: 44px;
      }

      td {
        min-width: 200px;
        max-width: 400px;
        padding: 4px 8px;
        border-right: 1px solid #ebeef5;
        border-bottom: 1px solid #ebeef5;
      }
    }

    .el-tabs--border-card > .el-tabs__content {
      overflow: auto;
      min-height: 100%;
    }
  }

  .markdown-container {
    text-align: left;
  }
}

:deep(blockquote) {
  display: block;
  padding: 16px;
  margin: 0 0 24px;
  border-left: 8px solid #dddfe4;
  background: #eef0f4;
  overflow: auto;
  word-break: break-word !important;
  p {
    font-size: 14px;
    line-height: 22px;
    color: #555666;
    font-weight: 400;
    margin-bottom: 0;
  }
}
</style>
